package CSS::Parse::AsBuilder::Selectors;

$VERSION = 0.03;

use strict;
use warnings;

use Data::Dumper;
use Carp;

use CSS::Builder::Selector;
use CSS::Builder::Selector::Class;
use CSS::Builder::Selector::Child;
use CSS::Builder::Selector::Type;
use CSS::Builder::Selector::Descendant;
use CSS::Builder::Selector::Id;
use CSS::Builder::Selector::Pseudo;
use CSS::Builder::Selector::Universal;
use CSS::Builder::Selector::Sibling;
use CSS::Builder::Selector::Attribute;

sub new {
      my $class = shift;
      my $self = bless {}, $class;
      return $self;
}

sub build_selectors {
      my ($self , $selectors) = @_;
      
      my @res = ();
      # parse the selectors
      foreach my $selector (split(/\s*,\s*/, $selectors)){
	    push @res , $self->new_selector($selector);
      }
      
      return \@res;
}

sub new_selector {
      my ($self , $selector) = @_;
      
      my $sel_obj = CSS::Builder::Selector->new(original => $selector);
      
      #make some optimizations with string for the parser
      $selector = $self->opt_for_parse($selector);
      
      #parse $selector
      my $method = 'new_' . $self->get_method($selector);
      my $res = $self->$method($selector);
      
      $sel_obj->list_push($res);
      return $sel_obj;
}

sub new_sibling {
      my ($self , $string) = @_;
      
      croak "No proper child string"
            unless $string =~ /\+/;
      
      my $sibling = CSS::Builder::Selector::Sibling->new(original => $string);
      my @children = split /\+/ , $string;
      foreach my $c (@children) {
            my $method = 'new_' . $self->get_method($c);
            my $res = $self->$method($c);
            $sibling->list_push($res);
      }
      return $sibling;
}

sub new_child {
      my ($self , $string) = @_;
      
      croak "No proper child string"
            unless $string =~ />/;
      
      my $child = CSS::Builder::Selector::Child->new(original => $string);
      my @children = split />/ , $string;
      foreach my $c (@children) {
            my $method = 'new_' . $self->get_method($c);
            my $res = $self->$method($c);
            $child->list_push($res);
      }
      return $child;
}

sub new_descendant {
      my ($self , $string) = @_;
      
      croak "No proper descendant string"
            unless $string =~ / /;
      
      my $des = CSS::Builder::Selector::Descendant->new(original => $string);
      my @descendants = split / / , $string;
      foreach my $d (@descendants) {
            my $method = 'new_' . $self->get_method($d);
            my $res = $self->$method($d);
            $des->list_push($res);
      }
      return $des;
}

sub new_type {
      my ($self , $string) = @_;
      
      my $type = CSS::Builder::Selector::Type->new(original => $string);
      $string =~ /^([^[.#: ]+)/;
      $type->type($1);
      
      #further analyze the string
      my $res = $self->selector_analyze($string);
      if ($res) {
            my $method = 'new_' . $res->[0];
            $type->list_push($self->$method($res->[1]));
      }
      
      return $type;
}

sub new_class {
      my ($self , $string) = @_;
      croak "Not proper class string passed to new_class"
            unless $string =~ /^\./;
      
      my $class = CSS::Builder::Selector::Class->new(original => $string);
      $string =~ /^\.([^[#: .]+)/;
      $class->class($1);
      
      #further analyze the string
      my $res = $self->futher_analyze(substr $string , 1 , length($string));
      $class->list_push($res) if $res;
      
      return $class;
}

sub new_id {
      my ($self , $string) = @_;
      croak "Not proper id string passed to new_id"
            unless $string =~ /^#/;
      
      my $id = CSS::Builder::Selector::Id->new(original => $string);
      $string =~ /^#([^[.: ]+)/;
      $id->prop_id($1);
      
      #further analyze the string
      my $res = $self->futher_analyze(substr $string , 1 , length($string));
      $id->list_push($res) if $res;
      
      return $id;
}

sub new_pseudo {
      my ($self , $string) = @_;
      croak "Not proper id string passed to new_id"
            unless $string =~ /^:/;
      
      my $pseudo = CSS::Builder::Selector::Pseudo->new(
                                                original => $string,
                                                pseudo  => $string
      );
      
      return $pseudo;
}

sub new_attribute {
      my ($self , $string) = @_;
      croak "Not proper class string passed to new_class"
            unless $string =~ /^\[/;
      
      my $attr = CSS::Builder::Selector::Attribute->new(original => $string);
      $string =~ /^\[([^]]+)\]/;
      $attr->attribute($1);
      
      unless ($1) {
            die print "STRING: $string\n";
      }
      
      #further analyze the string
      my $res = $self->futher_analyze(substr $string , length($attr->attribute) , length($string));
      $attr->list_push($res) if $res;
      
      return $attr;
}

sub new_universal {
      my ($self , $string) = @_;
      
      return CSS::Builder::Selector::Universal->new(original => $string);
}

sub futher_analyze {
      my ($self , $string) = @_;
      
      my $res = $self->selector_analyze($string);
      if ($res) {
            my $method = 'new_' . $res->[0];
            return $self->$method($res->[1]);
      }
      
}

sub selector_analyze {
      my ($self , $string) = @_;
      
      my @chars = split // , $string;
      
      my %map = (
            class      => '^\.',
            id         => '^#',
            pseudo     => '^:',
            attribute  => '^\[',
      );
      
      my @res = ();
      
      for (my $i = 0; $i <= $#chars; $i++) {
            foreach my $method (keys %map) {
                  if ($chars[$i] =~ /$map{$method}/) {
                        
                        push @res , $method;
                        push @res , join "" , @chars[ $i .. $#chars ];
                        
                        return \@res;
                  }
            }
      }
}

sub get_method {
      my ($self , $string) = @_;
      
      my @map = (
            sibling    => '\+',
            child      => '>',
            descendant => ' ',
            attribute  => '^\[',
            class      => '^\.',
            id         => '^#',
            pseudo     => '^:',
            universal  => '^\*',
      );
      
      for (my $i = 0; $i <= $#map; $i += 2) {
            return $map[$i] if $string =~ /$map[$i + 1]/;
      }
      
      return 'type';
}

sub opt_for_parse {
      my ($self , $string) = @_;
      
      $string =~ s/\*/ \* /g;            # html*div*span -> html * div * div
      $string =~ s/(\s+)?\+(\s+)?/\+/g;  # html   +    div -> html + div 
      $string =~ s/(\s+)?>(\s+)?/>/g;    # html     >    div -> html>div
      $string =~ s/\s+/ /g;              # html     div -> html div
      
      $string =~ s/\[(\w+)(\s+)?/\[$1/;     # a[href ^="gopher://"] -> a[href^="gopher://"]
      #print $string , "\n";
      
      
      return $string;
}

1;